/*
 * File:    MD5.java
 * Created: 06-Nov-2007
 * 
 * by Kenji Hollis <kenji@bitgatesoftware.com>
 * Released under the Creative Commons License version 2.5
 */

package com.bitgate.util.encoder;

/**
 * This code implements the MD5 message-digest algorithm.
 * The algorithm is due to Ron Rivest.  This code was
 * written by Colin Plumb in 1993, no copyright is claimed.
 * This code is in the public domain; do with it what you wish.
 *
 * Equivalent code is available from RSA Data Security, Inc.
 * This code has been tested against that, and is equivalent,
 * except that you don't need to include two pages of legalese
 * with every copy.
 *
 * To compute the message digest of a chunk of bytes, declare an
 * MD5Context structure, pass it to MD5Init, call MD5Update as
 * needed on buffers full of bytes, and then call MD5Final, which
 * will fill a supplied 16-byte array with the digest.
 *
 * @author Jon Howell &lt;jonh@cs.dartmouth.edu&gt;, Kenji Hollis &lt;kenji@nuklees.com&gt;
 * @since 0.1
 */
@SuppressWarnings("synthetic-access")
public class MD5 {
	
    private int buf[];      // These were originally unsigned ints.
    // This Java code makes an effort to avoid sign traps.
    // buf[] is where the hash accumulates.
private long bits;      // This is the count of bits hashed so far.
private byte in[];      // This is a buffer where we stash bytes until we have
    // enough (64) to perform a transform operation.
private int inint[];
    // inint[] used and discarded inside transform(),
    // but why allocate it over and over?
    // (In the C version this is allocated on the stack.)

/**
* The constructor.  Initializes MD5 hash accumulator.
*/
public MD5() {
this.buf = new int[4];
// fill the hash accumulator with a seed value
this.buf[0] = 0x67452301;
this.buf[1] = 0xefcdab89;
this.buf[2] = 0x98badcfe;
this.buf[3] = 0x10325476;

// initially, we've hashed zero bits
this.bits = 0L;

this.in = new byte[64];
this.inint = new int[16];
}

/**
* Returns the MD5 of a line of data.
*
* @param data The string to calculate an MD5 of.
* @return String containing the MD5 hash of the string.
*/
public static String md5(String data)
{
MD5 md5 = new MD5();
byte out[] = new byte[16];

md5.update(data.getBytes(), data.length());
md5.md5final(out);
return md5.dumpBytes(out);
}

/**
* Function to add data to an already MD5'd buffer.
*
* @param newbuf Byte array to add
*/
public void update(byte[] newbuf) {
update(newbuf, 0, newbuf.length);
}

/**
* Function to add data to an already MD5'd buffer.  This one lets you specify a length.
*
* @param newbuf Byte array to add
* @param length Length of byte(s) to add
*/
public void update(byte[] newbuf, int length) {
update(newbuf, 0, length);
}

/**
* Function to add data to an already MD5'd buffer.  This lets you specify the start-of-buffer position,
* and length to copy.
*
* @param newbuf Byte array to add
* @param bufstart Where in the <code>newbuf</code> to start from
* @param buflen Length of bytes to copy
*/
public void update(byte[] newbuf, int bufstart, int buflen) {
int t;
int len = buflen;
int storedBufStart = bufstart;

// shash old bits value for the "Bytes already in" computation
// just below.
t = (int) this.bits; // (int) cast should just drop high bits, I hope

/* update bitcount */
/* the C code used two 32-bit ints separately, and carefully
* ensured that the carry carried.
* Java has a 64-bit long, which is just what the code really wants.
*/
this.bits += (len << 3);

t = (t >>> 3) & 0x3f;   /* Bytes already in this->in */

/* Handle any leading odd-sized chunks */
/* (that is, any left-over chunk left by last update() */

if (t!=0) {
int p = t;
t = 64 - t;
if (len < t) {
    System.arraycopy(newbuf, bufstart, this.in, p, len);
    return;
}
System.arraycopy(newbuf, bufstart, this.in, p, t);
transform();
storedBufStart += t;
len -= t;
}

/* Process data in 64-byte chunks */
while (len >= 64) {
System.arraycopy(newbuf, bufstart, this.in, 0, 64);
transform();
storedBufStart += 64;
len -= 64;
}

/* Handle any remaining bytes of data. */
/* that is, stash them for the next update(). */
System.arraycopy(newbuf, storedBufStart, this.in, 0, len);
}

/**
* Final wrapup - pad to 64-byte boundary with the bit pattern
* 1 0* (64-bit count of bits processed, MSB-first)
*/
public void md5final(byte[] digest) {
/* "final" is a poor method name in Java. :v) */
int count;
int p;          // in original code, this is a pointer; in this java code
            // it's an index into the array this->in.

/* Compute number of bytes mod 64 */
count = (int) ((this.bits >>> 3) & 0x3F);

/* Set the first char of padding to 0x80.  This is safe since there is
always at least one byte free */
p = count;
this.in[p++] = (byte) 0x80;

/* Bytes of padding needed to make 64 bytes */
count = 64 - 1 - count;

/* Pad out to 56 mod 64 */
if (count < 8) {
/* Two lots of padding:  Pad the first block to 64 bytes */
zeroByteArray(this.in, p, count);
transform();

/* Now fill the next block with 56 bytes */
zeroByteArray(this.in, 0, 56);
} else {
/* Pad block to 56 bytes */
zeroByteArray(this.in, p, count - 8);
}

/* Append length in bits and transform */
// Could use a PUT_64BIT... func here. This is a fairly
// direct translation from the C code, where bits was an array
// of two 32-bit ints.
int lowbits =   (int) this.bits;
int highbits =  (int) (this.bits >>> 32);
PUT_32BIT_LSB_FIRST(this.in, 56, lowbits);
PUT_32BIT_LSB_FIRST(this.in, 60, highbits);

transform();
PUT_32BIT_LSB_FIRST(digest,  0, this.buf[0]);
PUT_32BIT_LSB_FIRST(digest,  4, this.buf[1]);
PUT_32BIT_LSB_FIRST(digest,  8, this.buf[2]);
PUT_32BIT_LSB_FIRST(digest, 12, this.buf[3]);

/* zero sensitive data */
/* notice this misses any sneaking out on the stack. The C
* version uses registers in some spots, perhaps because
* they care about this.
*/
zeroByteArray(this.in);
zeroIntArray(this.buf);
this.bits = 0;
zeroIntArray(this.inint);
}

/////////////////////////////////////////////////////////////////////
// Below here ye will only finde private functions                 //
/////////////////////////////////////////////////////////////////////

// There must be a way to do these functions that's
// built into Java, and I just haven't noticed it yet.

private void zeroByteArray(byte[] a) {
zeroByteArray(a, 0, a.length);
}

private void zeroByteArray(byte[] a, int start, int length) {
setByteArray(a, (byte) 0, start, length);
}

private void setByteArray(byte[] a, byte val, int start, int length) {
int i;
int end = start+length;
for (i=start; i<end; i++) {
a[i] = val;
}
}

private void zeroIntArray(int[] a) {
zeroIntArray(a, 0, a.length);
}

private void zeroIntArray(int[] a, int start, int length) {
setIntArray(a, 0, start, length);
}

private void setIntArray(int[] a, int val, int start, int length) {
int i;
int end = start+length;
for (i=start; i<end; i++) {
a[i] = val;
}
}

// In the C version, a call to MD5STEP is a macro-in-a-macro.
// In this Java version, we pass an Fcore object to represent the
// inner macro, and the MD5STEP() method performs the work of
// the outer macro. It would be good if this could all get
// inlined, but it would take a pretty aggressive compiler to
// inline away the dynamic method lookup made by MD5STEP to
// get to the Fcore.f function.

private abstract class Fcore {
abstract int f(int x, int y, int z);
}

private final Fcore F1 = new Fcore() {
@Override
int f(int x, int y, int z) { return (z ^ (x & (y ^ z))); }
};

private final Fcore F2 = new Fcore() {
@Override
int f(int x, int y, int z) { return (y ^ (z & (x ^ y))); }
};

private final Fcore F3 = new Fcore() {
@Override
int f(int x, int y, int z) { return (x ^ y ^ z); }
};

private final Fcore F4 = new Fcore() {
@Override
int f(int x, int y, int z) { return (y ^ (x | ~z)); }
};

private int MD5STEP(Fcore f, int w, int x, int y, int z, int data, int s) {
int local_w = w;

local_w += f.f(x, y, z) + data;
local_w = local_w << s | local_w >>> (32-s);
local_w += x;
return local_w;
}

private void transform() {
/* load in[] byte array into an internal int array */
int i;
int[] localInint = new int[16];

for (i=0; i<16; i++) {
localInint[i] = GET_32BIT_LSB_FIRST(this.in, 4*i);
}

int a, b, c, d;
a = this.buf[0];
b = this.buf[1];
c = this.buf[2];
d = this.buf[3];

a = MD5STEP(this.F1, a, b, c, d, localInint[0]        + 0xd76aa478, 7);
d = MD5STEP(this.F1, d, a, b, c, localInint[1]        + 0xe8c7b756, 12);
c = MD5STEP(this.F1, c, d, a, b, localInint[2]        + 0x242070db, 17);
b = MD5STEP(this.F1, b, c, d, a, localInint[3]        + 0xc1bdceee, 22);
a = MD5STEP(this.F1, a, b, c, d, localInint[4]        + 0xf57c0faf, 7);
d = MD5STEP(this.F1, d, a, b, c, localInint[5]        + 0x4787c62a, 12);
c = MD5STEP(this.F1, c, d, a, b, localInint[6]        + 0xa8304613, 17);
b = MD5STEP(this.F1, b, c, d, a, localInint[7]        + 0xfd469501, 22);
a = MD5STEP(this.F1, a, b, c, d, localInint[8]        + 0x698098d8, 7);
d = MD5STEP(this.F1, d, a, b, c, localInint[9]        + 0x8b44f7af, 12);
c = MD5STEP(this.F1, c, d, a, b, localInint[10]       + 0xffff5bb1, 17);
b = MD5STEP(this.F1, b, c, d, a, localInint[11]       + 0x895cd7be, 22);
a = MD5STEP(this.F1, a, b, c, d, localInint[12]       + 0x6b901122, 7);
d = MD5STEP(this.F1, d, a, b, c, localInint[13]       + 0xfd987193, 12);
c = MD5STEP(this.F1, c, d, a, b, localInint[14]       + 0xa679438e, 17);
b = MD5STEP(this.F1, b, c, d, a, localInint[15]       + 0x49b40821, 22);

a = MD5STEP(this.F2, a, b, c, d, localInint[1]        + 0xf61e2562, 5);
d = MD5STEP(this.F2, d, a, b, c, localInint[6]        + 0xc040b340, 9);
c = MD5STEP(this.F2, c, d, a, b, localInint[11]       + 0x265e5a51, 14);
b = MD5STEP(this.F2, b, c, d, a, localInint[0]        + 0xe9b6c7aa, 20);
a = MD5STEP(this.F2, a, b, c, d, localInint[5]        + 0xd62f105d, 5);
d = MD5STEP(this.F2, d, a, b, c, localInint[10]       + 0x02441453, 9);
c = MD5STEP(this.F2, c, d, a, b, localInint[15]       + 0xd8a1e681, 14);
b = MD5STEP(this.F2, b, c, d, a, localInint[4]        + 0xe7d3fbc8, 20);
a = MD5STEP(this.F2, a, b, c, d, localInint[9]        + 0x21e1cde6, 5);
d = MD5STEP(this.F2, d, a, b, c, localInint[14]       + 0xc33707d6, 9);
c = MD5STEP(this.F2, c, d, a, b, localInint[3]        + 0xf4d50d87, 14);
b = MD5STEP(this.F2, b, c, d, a, localInint[8]        + 0x455a14ed, 20);
a = MD5STEP(this.F2, a, b, c, d, localInint[13]       + 0xa9e3e905, 5);
d = MD5STEP(this.F2, d, a, b, c, localInint[2]        + 0xfcefa3f8, 9);
c = MD5STEP(this.F2, c, d, a, b, localInint[7]        + 0x676f02d9, 14);
b = MD5STEP(this.F2, b, c, d, a, localInint[12]       + 0x8d2a4c8a, 20);

a = MD5STEP(this.F3, a, b, c, d, localInint[5]        + 0xfffa3942, 4);
d = MD5STEP(this.F3, d, a, b, c, localInint[8]        + 0x8771f681, 11);
c = MD5STEP(this.F3, c, d, a, b, localInint[11]       + 0x6d9d6122, 16);
b = MD5STEP(this.F3, b, c, d, a, localInint[14]       + 0xfde5380c, 23);
a = MD5STEP(this.F3, a, b, c, d, localInint[1]        + 0xa4beea44, 4);
d = MD5STEP(this.F3, d, a, b, c, localInint[4]        + 0x4bdecfa9, 11);
c = MD5STEP(this.F3, c, d, a, b, localInint[7]        + 0xf6bb4b60, 16);
b = MD5STEP(this.F3, b, c, d, a, localInint[10]       + 0xbebfbc70, 23);
a = MD5STEP(this.F3, a, b, c, d, localInint[13]       + 0x289b7ec6, 4);
d = MD5STEP(this.F3, d, a, b, c, localInint[0]        + 0xeaa127fa, 11);
c = MD5STEP(this.F3, c, d, a, b, localInint[3]        + 0xd4ef3085, 16);
b = MD5STEP(this.F3, b, c, d, a, localInint[6]        + 0x04881d05, 23);
a = MD5STEP(this.F3, a, b, c, d, localInint[9]        + 0xd9d4d039, 4);
d = MD5STEP(this.F3, d, a, b, c, localInint[12]       + 0xe6db99e5, 11);
c = MD5STEP(this.F3, c, d, a, b, localInint[15]       + 0x1fa27cf8, 16);
b = MD5STEP(this.F3, b, c, d, a, localInint[2]        + 0xc4ac5665, 23);

a = MD5STEP(this.F4, a, b, c, d, localInint[0]        + 0xf4292244, 6);
d = MD5STEP(this.F4, d, a, b, c, localInint[7]        + 0x432aff97, 10);
c = MD5STEP(this.F4, c, d, a, b, localInint[14]       + 0xab9423a7, 15);
b = MD5STEP(this.F4, b, c, d, a, localInint[5]        + 0xfc93a039, 21);
a = MD5STEP(this.F4, a, b, c, d, localInint[12]       + 0x655b59c3, 6);
d = MD5STEP(this.F4, d, a, b, c, localInint[3]        + 0x8f0ccc92, 10);
c = MD5STEP(this.F4, c, d, a, b, localInint[10]       + 0xffeff47d, 15);
b = MD5STEP(this.F4, b, c, d, a, localInint[1]        + 0x85845dd1, 21);
a = MD5STEP(this.F4, a, b, c, d, localInint[8]        + 0x6fa87e4f, 6);
d = MD5STEP(this.F4, d, a, b, c, localInint[15]       + 0xfe2ce6e0, 10);
c = MD5STEP(this.F4, c, d, a, b, localInint[6]        + 0xa3014314, 15);
b = MD5STEP(this.F4, b, c, d, a, localInint[13]       + 0x4e0811a1, 21);
a = MD5STEP(this.F4, a, b, c, d, localInint[4]        + 0xf7537e82, 6);
d = MD5STEP(this.F4, d, a, b, c, localInint[11]       + 0xbd3af235, 10);
c = MD5STEP(this.F4, c, d, a, b, localInint[2]        + 0x2ad7d2bb, 15);
b = MD5STEP(this.F4, b, c, d, a, localInint[9]        + 0xeb86d391, 21);

this.buf[0] += a;
this.buf[1] += b;
this.buf[2] += c;
this.buf[3] += d;
}

private int GET_32BIT_LSB_FIRST(byte[] b, int off) {
return
((b[off+0]&0xff)) |
((b[off+1]&0xff) << 8) |
((b[off+2]&0xff) << 16) |
((b[off+3]&0xff) << 24);
}

private void PUT_32BIT_LSB_FIRST(byte[] b, int off, int value) {
b[off+0] = (byte) (value                & 0xff);
b[off+1] = (byte) ((value >> 8) & 0xff);
b[off+2] = (byte) ((value >> 16)& 0xff);
b[off+3] = (byte) ((value >> 24)& 0xff);
}

/**
* This function dumps the bytes of a string to hexadecimal format.
*
* @param bytes Bytes to dump
* @return String String containing the formatted data.
*/
public String dumpBytes(byte[] bytes) {
int i;
StringBuffer sb = new StringBuffer();
for (i=0; i<bytes.length; i++) {
if (i%32 == 0 && i!=0) {
    sb.append("\n");
}
String s = Integer.toHexString(bytes[i]);
if (s.length() < 2) {
    s = "0"+s;
}
if (s.length() > 2) {
    s = s.substring(s.length()-2);
}
sb.append(s);
}
return sb.toString();
}
	
}